
#!/usr/bin/env python3
import os
import re
import sys
from collections import defaultdict
from pathlib import Path
import argparse


class PackageDependencyAnalyzer:
    """分析源代码包依赖关系的工具"""
    
    def __init__(self, root_dir):
        """初始化分析器"""
        self.root_dir = Path(root_dir)
        self.packages = {}  # 存储包信息：{包名: {‘depends': [依赖项]}}
        self.dependency_graph = defaultdict(list)  # 依赖关系图
        
    def analyze(self):
        """分析目录中的所有包"""
        self._find_packages()
        self._extract_dependencies()
        return self.packages, self.dependency_graph
    
    def _find_packages(self):
        """查找所有spec文件"""
        # print(f"在目录 {self.root_dir} 中查找spec文件名称")
        #在root_dir中查找所有.spec文件
        spec_files = self.root_dir.glob("*.spec")
        for file in spec_files:
            if file.stem == "agiros-base":
                self.packages['ros-base'] = {
                    'spec_path': str(file),
                    'depends': []
                }
            else:
                self.packages[file.stem] = {
                    'spec_path': str(file),
                    'depends': []
                }
    
    def _extract_dependencies(self):
        """从spec文件中提取依赖"""
        # print("\n提取依赖关系...")
        for pkg_name, pkg_info in self.packages.items():
            try:
                # 提取Requires和BuildRequires字段
                depends = self._parse_depends(pkg_info['spec_path'])
                pkg_info['depends'] = depends

                # 构建依赖
                for dep in depends:
                    # 只考虑自有包作为依赖，三方包不考虑
                    if dep in self.packages:
                        self.dependency_graph[pkg_name].append(dep)
                
                # print(f"{pkg_name} 的依赖: {', '.join(depends) if depends else '无'}")
            except Exception as e:
                print(f"警告: 无法解析 {pkg_info['spec_path']} 的依赖: {e}")

    def _parse_depends(self, spec_file):
        """解析Requires和BuildRequires字段，提取依赖包名"""
        dependencies = set()
        try:
            with open(spec_file, 'r', encoding='utf-8') as f:
                content = f.read()
                # 匹配Requires和BuildRequires行
                pattern = r'^(Requires|BuildRequires):\s*(.*)$'
                matches = re.findall(pattern, content, re.MULTILINE)
                for _, deps_line in matches:
                    # 分割依赖项并过滤空值
                    deps = [dep.strip() for dep in deps_line.split() if dep.strip()]
                    for dep in deps:
                        # 提取以agiros-开头的依赖，去除前缀"agiros-%{ros_distro}-",ros_distro作为普通字符串，不作为变量；去除后缀-devel
                        if dep.startswith('agiros-%{ros_distro}-'):
                            dependencies.add(dep.replace('agiros-%{ros_distro}-', '', 1).replace('-devel', ''))
        except Exception as e:
            print(f"读取文件 {spec_file} 时出错: {e}")
        
        return dependencies

    def generate_compile_order(self):
        """生成包的编译顺序（拓扑排序）"""
        in_degree = {pkg: 0 for pkg in self.packages}
        for pkg, deps in self.dependency_graph.items():
            for dep in deps:
                if dep in in_degree:
                    in_degree[dep] += 1
    
        queue = [pkg for pkg in in_degree if in_degree[pkg] == 0]
        compile_order = []
    
        while queue:
            current_pkg = queue.pop(0)
            compile_order.append(current_pkg)
        
            for dep in self.dependency_graph.get(current_pkg, []):
                in_degree[dep] -= 1
                if in_degree[dep] == 0:
                    queue.append(dep)
    
        if len(compile_order) != len(self.packages):
            cycle_packages = set(self.packages.keys()) - set(compile_order)
            print(f"警告：发现循环依赖！无法生成完整编译顺序。")
            print(f"循环依赖的包：{', '.join(cycle_packages)}")
    
        return compile_order[::-1]

    def generate_min_dependset(self, initial_pkgs):
        """
        生成初始包列表中所有包依赖的包的最小集合，包括初始包自己在内
        
        Args:
            initial_pkgs (list): 初始包名列表
            
        Returns:
            set: 包含所有依赖包的最小集合
        """
        # 初始化依赖集合，包含所有初始包
        depend_set = set(initial_pkgs)
        
        # 递归函数，用于收集依赖
        def collect_dependencies(pkg):
            # 遍历当前包的所有依赖
            for dep in self.dependency_graph.get(pkg, []):
                # 如果依赖包不在集合中，添加它并递归收集它的依赖

                if dep not in depend_set:
                    depend_set.add(dep)
                    collect_dependencies(dep)
        
        # 对每个初始包收集依赖
        for pkg in initial_pkgs:
            collect_dependencies(pkg)
        
        return depend_set

def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='分析目录中源代码包的依赖关系')
    parser.add_argument('--directory', default=os.path.join(os.getcwd(), 'build', 'SPECS'), help='包含源代码包的根目录 (默认: 当前目录/build/SPECS)')

    #增加运行参数 --build-order-file ，默认为当前目录下的orderedspec.conf
    parser.add_argument("--build-order-file", default=os.path.join(os.getcwd(), "orderedspec.conf"), help="保存编译顺序的文件路径，默认为当前目录下的orderedspec.conf")
    #增加运行参数 --min，默认值为False
    parser.add_argument("--min", action='store_true', default=False, help="是否生成最小依赖集合，默认为False")

    args = parser.parse_args()
    
    if not os.path.isdir(args.directory):
        print(f"错误: 目录 '{args.directory}' 不存在")
        sys.exit(1)
    
    analyzer = PackageDependencyAnalyzer(args.directory)
    packages, graph = analyzer.analyze()
    
    print(f"\n找到 {len(packages)} 个agiros包")


    print("\n生成编译顺序开始...")
    order = analyzer.generate_compile_order()
    
    #如果args.min为True，生成最小依赖集合
    if args.min:
        #初始包列表赋值为ros-base和turtlesim
        initial_pkgs = ["ros-base","turtlesim"]
        #生成最小依赖集合，写入文件minset
        min_depend_set = analyzer.generate_min_dependset(initial_pkgs)

        #最小依赖集合文件存在的话先删除
        if os.path.exists(os.path.join(os.getcwd(), "minset")):
            os.remove(os.path.join(os.getcwd(), "minset"))

        with open(os.path.join(os.getcwd(), "minset"), 'w') as f:
            for pkg in min_depend_set:
                f.write(f"{pkg}\n")
        print(f"最小依赖集合已写入文件 minset")

        #从order中筛选出minset中的包,并且要保持order中的顺序
        order = [pkg for pkg in order if pkg in min_depend_set]

    if os.path.exists(args.build_order_file):
        os.remove(args.build_order_file)

    # 遍历order，打印前10个元素
    for i, spec_file in enumerate(order[:10]):
        print(f"{i+1}. {spec_file}")
    
    print("...\n")

    with open(args.build_order_file, 'w') as f:
        # 遍历order，将所有元素加上后缀.spec后，写入orderspec.conf文件
        for pkg in order:
            if pkg == "ros-base":
                f.write(f"agi{pkg}.spec\n")
            else:
                f.write(f"{pkg}.spec\n")
    
    print("生成编译顺序完成！\n")

if __name__ == "__main__":
    main()


